package com.gitee.wsl.reflect.func

import com.gitee.wsl.reflect.func.annotation.Injected
import com.gitee.wsl.reflect.func.annotation.Name
import com.gitee.wsl.reflect.func.annotation.NoAutoArgumentUnwrapping
import com.gitee.wsl.reflect.func.call.binding.ArgumentBindings
import com.gitee.wsl.reflect.func.call.validate.FunctionCallValidator
import com.gitee.wsl.reflect.func.value.InputValue
import com.gitee.wsl.reflect.func.value.OutputValue
import com.gitee.wsl.reflect.func.value.ValueWrapper
import com.gitee.wsl.reflect.func.value.VoidValue
import timber.log.Timber
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf


fun<T : OutputValue<*>> Function(
    function: KFunction<T>,
    validators: List<FunctionCallValidator<T>> = emptyList()
):  Function<T> = KFunctionAdapter(function, validators)

/**
 * A Quarkdown [Function] adapted from a regular Kotlin [KFunction].
 * @param function Kotlin function to adapt
 */
class KFunctionAdapter<T : OutputValue<*>>(
    private val function: KFunction<T>,
    override val validators: List<FunctionCallValidator<T>> = emptyList()
) : Function<T> {
    /**
     * If the [Name] annotation is present on [function], the Quarkdown function name is set from there.
     * Otherwise, it is [function]'s original name.
     */
    override val name: String
        get() = function.findAnnotation<Name>()?.name ?: function.name

    @Suppress("UNCHECKED_CAST")
    override val parameters: List<FunctionParameter<*>>
        get() =
            function.parameters.map {
                FunctionParameter(
                    // If @Name is present, a custom name is set.
                    name = it.findAnnotation<Name>()?.name ?: it.name ?: "<unnamed parameter>",
                    type = it.type.classifier as KClass<out InputValue<T>>,
                    index = it.index,
                    isOptional = it.isOptional,
                    isInjected = it.hasAnnotation<Injected>(),
                )
            }

//    override val validators: List<FunctionCallValidator<T>>
//        get() = emptyList<FunctionCallValidator<T>>()
//            buildList {
//                function.findAnnotation<OnlyForDocumentType>()?.toValidator<T>()?.let(::add)
//                function.findAnnotation<NotForDocumentType>()?.toValidator<T>()?.let(::add)
//            }

    override val invoke: (ArgumentBindings) -> T
        get() = { bindings ->
            val args =
                bindings.asSequence().associate { (parameter, argument) ->
                    // Corresponding KParameter.
                    val param = function.parameters[parameter.index]

                    // The argument is unwrapped unless the value class specifies not to.
                    // An example of a disabled unwrapping is DynamicValue, which is used to pass dynamically typed values as-is.
                    val arg =
                        argument.value.let {
                            if (it::class.hasAnnotation<NoAutoArgumentUnwrapping>()) it else it.unwrappedValue
                        }

                    param to arg
                }

            // Call the KFunction.
            try {
                function.callBy(args)
            } catch (e: Throwable) {
                // Exceptions thrown within the called function are converted to Quarkdown exceptions
                // and handled accordingly by the pipeline's function expander component.
                Timber.d("(expected, received): " + args.map { it.key.type to it.value })

                // If the exception comes from a nested function call, the source function is retrieved.
                // Otherwise, this function becomes the source.
                //val source: Function<*> = (e.targetException as? FunctionException)?.function ?: this

                //throw FunctionRuntimeException(source, e.targetException)
                throw e
            }
        }
}

fun<T> Function2(
    function: KFunction<T>,
    validators: List<FunctionCallValidator<OutputValue<T>>> = emptyList()
):  Function<OutputValue<T>> = KFunctionAdapter2(function, validators)

class KFunctionAdapter2<T>(
    private val function: KFunction<T>,
    override val validators: List<FunctionCallValidator<OutputValue<T>>> = emptyList()
) : Function<OutputValue<T>> {
    /**
     * If the [Name] annotation is present on [function], the Quarkdown function name is set from there.
     * Otherwise, it is [function]'s original name.
     */
    override val name: String
        get() = function.findAnnotation<Name>()?.name ?: function.name

    @Suppress("UNCHECKED_CAST")
    override val parameters: List<FunctionParameter<*>>
        get() =
            function.parameters.map {
                FunctionParameter(
                    // If @Name is present, a custom name is set.
                    name = it.findAnnotation<Name>()?.name ?: it.name ?: "<unnamed parameter>",
                    type = it.type.classifier as KClass<out InputValue<T>>,
                    index = it.index,
                    isOptional = it.isOptional,
                    isInjected = it.hasAnnotation<Injected>(),
                )
            }

//    override val validators: List<FunctionCallValidator<T>>
//        get() = emptyList<FunctionCallValidator<T>>()
//            buildList {
//                function.findAnnotation<OnlyForDocumentType>()?.toValidator<T>()?.let(::add)
//                function.findAnnotation<NotForDocumentType>()?.toValidator<T>()?.let(::add)
//            }

    override val invoke: (ArgumentBindings) -> OutputValue<T>
        get() = { bindings ->
            val args =
                bindings.asSequence().associate { (parameter, argument) ->
                    // Corresponding KParameter.
                    val param = function.parameters[parameter.index]

                    // The argument is unwrapped unless the value class specifies not to.
                    // An example of a disabled unwrapping is DynamicValue, which is used to pass dynamically typed values as-is.
                    val arg =
                        argument.value.let {
                            if (it::class.hasAnnotation<NoAutoArgumentUnwrapping>()) it else it.unwrappedValue
                        }

                    param to arg
                }


            // Call the KFunction.
            try {
                val type = function.returnType
                when {
                    type == Unit::class -> {
                        function.callBy(args)
                        VoidValue as OutputValue<T>
                    }
                    type.isSubtypeOf(typeOf<OutputValue<*>>()) -> {
                        function.callBy(args) as OutputValue<T>
                    }
                    else -> {
                        ValueWrapper(function.callBy(args))
                    }
                }
            } catch (e: Throwable) {
                // Exceptions thrown within the called function are converted to Quarkdown exceptions
                // and handled accordingly by the pipeline's function expander component.
                Timber.d("(expected, received): " + args.map { it.key.type to it.value })

                // If the exception comes from a nested function call, the source function is retrieved.
                // Otherwise, this function becomes the source.
                //val source: Function<*> = (e.targetException as? FunctionException)?.function ?: this

                //throw FunctionRuntimeException(source, e.targetException)
                throw e
            }
        }
}